require 'scanner/states/start_state'
require 'scanner/states/error_state'
require 'logger'

class Scanner
  attr_reader :line_num
  
  def initialize(file_name)
    @source_file = file_name
    @state = StartState
    @state.on_enter(nil, "")
    @line_num = 1
    
    @log = Logger.new(STDOUT)
    @log.progname = "Scanner"
    @log.datetime_format = ""
    @log.level = Logger::WARN
  end
  
  def next_token
    if(!@source_file.closed?)
      until((char = @source_file.getc).nil?)
        token = process_char(char.chr)
        return token if returnable(token)
      end
      token = process_char(nil)
      @source_file.close
      return token if returnable(token)
      return Token.new(:EOF, 'EOF')
    else
      return Token.new(:EOF, 'EOF')
    end
  end

  def returnable(token)
    t = token.token_type unless token.nil?
    return (!token.nil? and t != :CommentLine and t != :CommentBlock)
  end
  
  def process_char(char)
    @line_num = @line_num + 1 if(char == "\n")
    previous = @state
    @log.debug "  Processing Character: '#{char}'"
    @log.debug "    State: #{previous}"
    token = advance_state(char)
    @log.debug "    Next State: #{@state}"
    @log.debug "    Token: '#{token}'"
    if @state == StartState
      if !token.nil?
        if token.token_type == :Identifier
          token = process_word(token)
        elsif token.token_type == :Operator
          token = process_operator(token)
        end
      end
      advance_state(char)
      return token
    elsif @state == ErrorState
      raise "Unexpected Character #{char} near #{@buffer}"
    else
      return nil
    end    
  end
  
  def advance_state(char)
    next_state = @state.next_state(char)
    token = @state.on_exit
    next_state.on_enter(token, char)
    @state = next_state
    return token
  end
  
  def process_word(token)
    literal = token.literal
    @log.debug "Processing Word: #{literal}"
    return case(literal)
    when "program":
      Token.new(:Keyword_program, :program)
    when "is":
      Token.new(:Keyword_is, :is)
    when "begin":
      Token.new(:Keyword_begin, :begin)
    when "end":
      Token.new(:Keyword_end, :end)
    when "function":
      Token.new(:Keyword_function, :function)
    when "returns":
      Token.new(:Keyword_returns, :returns)
    when "integer":
      Token.new(:Keyword_integer, :integer)
    when "character":
      Token.new(:Keyword_character, :character)
    when "string":
      Token.new(:Keyword_string, :string)
    when "if":
      Token.new(:Keyword_if, :if)
    when "then":
      Token.new(:Keyword_then, :then)
    when "else":
      Token.new(:Keyword_else, :else)
    when "while":
      Token.new(:Keyword_while, :while)
    else
      token
    end
  end
  
  def process_operator(token)
    literal = token.literal
    @log.debug "Processing Operator: #{literal}"
    return case(literal)
    when ";":
      Token.new(:Operator_semicolon, ';')
    when "(":
      Token.new(:Operator_left_paren, '(')
    when ")":
      Token.new(:Operator_right_paren, ')')
    when "[":
      Token.new(:Operator_left_square, '[')
    when "]":
      Token.new(:Operator_right_square, ']')
    when ",":
      Token.new(:Operator_comma, ',')
    when ":=":
      Token.new(:Operator_colon_equal, ':=')
    when "+":
      Token.new(:Operator_plus, '+')
    when "-":
      Token.new(:Operator_minus, '-')
    when "&":
      Token.new(:Operator_amp, '&')
    when "|":
      Token.new(:Operator_pipe, '|')
    when "<":
      Token.new(:Operator_less, '<')
    when "<=":
      Token.new(:Operator_less_equal, '<=')
    when ">":
      Token.new(:Operator_greater, '>')
    when ">=":
      Token.new(:Operator_greater_equal, '>=')
    when "==":
      Token.new(:Operator_equal_equal, '==')
    when "!=":
      Token.new(:Operator_not_equal, '!=')
    when "*":
      Token.new(:Operator_star, '*')
    when "/":
      Token.new(:Operator_slash, '/')
    else
      token
    end
  end
  
  def read_and_print
    char = nil
    while((char = @source_file.getc) != nil)
      puts char.chr
    end
  end
end
